﻿using System;
using System.Collections.Generic;
using System.Text;

using Microsoft.Scripting;
using Microsoft.Scripting.Actions;
using Microsoft.Scripting.Runtime;
using Microsoft.Scripting.Utils;
using Microsoft.Scripting.Ast;
using Microsoft.Linq.Expressions;

using DlrBasic.Parser.Ast;

namespace DlrBasic.Parser
{
    /// <summary>
    /// Class helper to generate program-lamba.
    /// Current implementation is high coupled with other Ast.* classes.
    /// TODO redisign
    /// </summary>
    class DlrBasicGenerator
    {
        DlrBasicLanguageContext context;
        SourceUnit sourceUnit;
        LambdaBuilder builder;
        Expression stateVariable;
        Expression memoryVariable;
        Expression userFunctionsVariable;
        Expression statementCallbackDelegate;
        public Dictionary<Statement, int> Map;
        public SortedDictionary<int, int> LineStarts;
        public Dictionary<Statement, Statement> NextStatements;
        private FunctionScope functionScope = null;
        private Dictionary<string, Expression> variables;

        Dictionary<Type, Expression> tempVariables = new Dictionary<Type, Expression>();

        internal DlrBasicGenerator(DlrBasicLanguageContext context, SourceUnit sourceUnit)
        {
            this.context = context;
            this.sourceUnit = sourceUnit;

            variables = new Dictionary<string, Expression>(StringComparer.InvariantCultureIgnoreCase);

            builder = Utils.Lambda(typeof(object), "<DlrBasic script>");
            builder.Global = true;
            stateVariable = builder.HiddenVariable(typeof(Runtime.DlrBasicState), "state");
            statementCallbackDelegate = builder.ClosedOverVariable(typeof(StatementCallback), "statementCallback");
            memoryVariable = builder.ClosedOverVariable(typeof(Runtime.DlrBasicMemory), "memory");
            userFunctionsVariable = builder.ClosedOverVariable(typeof(Runtime.DlrBasicUserFunctions), "userFunctions");
        }

        internal static LambdaExpression Generate(DlrBasicLanguageContext languageContext, DlrBasicNode parsed, SourceUnit sourceUnit)
        {
            DlrBasicGenerator generator = new DlrBasicGenerator(languageContext, sourceUnit);

            Expression body;

            if (parsed is BasicProgram)
            {
                BasicProgram program = ((BasicProgram)parsed);
                body = program.Generate(generator);
            }
            else
            {
                InteractiveLine line = ((InteractiveLine)parsed);
                body = line.Generate(generator);
            }            

            generator.builder.Body = body;
            return generator.builder.MakeLambda();
        }

        internal LambdaExpression CreateFunction(Type returnType, string name, FunctionParameter[] parameters, DlrBasicExpression functionBody)
        {
            functionScope = new FunctionScope(returnType, name, parameters);
            Expression body = functionBody.Generate(this);
            functionScope.SetBody(TypeConversionUtils.BuildConversionTo(returnType, body));

            LambdaExpression function = functionScope.ToLambdaExpression();
            functionScope = null;

            return function;
        }

        internal Expression GetFunctionParameter(string name)
        {
            return functionScope.GetParameter(name);
        }

        internal bool IsFunctionParameter(string name)
        {
            return functionScope != null && functionScope.IsParameterPresent(name);
        }

        internal Expression StatementCallbackDelegate
        {
            get { return statementCallbackDelegate; }
        }

        internal Expression MemoryVariable
        {
            get { return memoryVariable; }
        }
        
        internal Expression StateVariable
        {
            get { return stateVariable; }
        }

        internal Expression GetCurrentStatementVariable()
        {
            return Expression.Property(StateVariable, "CurrentStatement");
        }

        internal Expression UserFunctionsVariable
        {
            get { return userFunctionsVariable; }
        }

        ParameterExpression currentStateParameter = null;

        internal ParameterExpression CurrentStateParameter
        {
            get { return currentStateParameter; }
            set { currentStateParameter = value; }
        }

        internal StateMemberGenerator GetCurrentStateParameterGenerator()
        {
            return new StateMemberGenerator(CurrentStateParameter); 
        }

        internal Expression GetTempVariable(string name, Type type)
        {
            Expression tempVariable;
            if (!tempVariables.TryGetValue(type, out tempVariable))
            {
                tempVariable = builder.HiddenVariable(type, name);
            }
            return tempVariable;
        }

        internal int GetLineStatementNumber(int lineNumber)
        {
            int statement;
            if (LineStarts.TryGetValue(lineNumber, out statement))
                return statement;
            else
                throw new SyntaxErrorException(Properties.Resources.LineNumberNotFoundException);
        }

        internal int GetStatementNumber(Statement statement)
        {
            return Map[statement];
        }

        internal int GetNextStatementNumber(Statement statement)
        {
            Statement nextStatement;
            if (NextStatements.TryGetValue(statement, out nextStatement))
            {
                return Map[nextStatement];
            }
            else
                return -1;
        }

        internal int[] GetNextStatementNumbersMap()
        {
            List<int> nextStatementNumbers = new List<int>();
            foreach (KeyValuePair<Statement, Statement> pair in NextStatements)
            {
                int statementNumber = Map[pair.Key];
                while (nextStatementNumbers.Count <= statementNumber)
                {
                    nextStatementNumbers.Add(-1);
                }
                nextStatementNumbers[statementNumber] = Map[pair.Value];
            }
            return nextStatementNumbers.ToArray();
        }

        internal Statement GetNextStatement(Statement statement)
        {
            Statement result;
            NextStatements.TryGetValue(statement, out result);
            return result;
        }


        internal void GenerateStatementNumbers(BasicProgram program)
        {
            NumberStatementsVisitor visitor = new NumberStatementsVisitor();
            program.Visit(visitor);

            LineStarts = visitor.LineStarts;
            Map = visitor.Map;
            NextStatements = visitor.NextStatement;
        }

        internal void GenerateStatementNumbers(InteractiveLine line)
        {
            NumberStatementsVisitor visitor = new NumberStatementsVisitor();
            line.Visit(visitor);

            LineStarts = visitor.LineStarts;
            Map = visitor.Map;
            NextStatements = visitor.NextStatement;
        }

    }
}
